package sss

import (
	"testing"
	"errors"
	"strings"
)


func expectSelectors(selectors... string) string {
	const defaultStyle = " {\n\toverflow: auto;\n}\n"
	var buf strings.Builder
	for _, selector := range selectors {
		buf.WriteString(selector)
		buf.WriteString(defaultStyle)
	}
	return buf.String()
}

func testCompare(t *testing.T, expected string, s StyleSheet) {
	var output = strings.TrimPrefix(Output(s), Header)
	print(output)
	if output != expected {
		t.Fatal(errors.New("unexpected output"))
	}
}

func TestStyleDefaultValue(t *testing.T) {
	var expected = expectSelectors(".foo")
	testCompare(t, expected, StyleSheet {
		Rules: [] Rule { {
			Selector: ClassSelector { Name: "foo" },
		} },
	})
}

func TestMultiple(t *testing.T) {
	var expected = expectSelectors(".foo", ".bar")
	testCompare(t, expected, StyleSheet {
		Rules: [] Rule {
			{ Selector: ClassSelector { Name: "foo" } },
			{ Selector: ClassSelector { Name: "bar" } },
		},
	})
}

func TestNestedClass(t *testing.T) {
	var expected = expectSelectors (
		".outer",
		".outer .inner-A",
		".outer .inner-B",
		".outer .inner-B .inner-B-inner",
	)
	testCompare(t, expected, StyleSheet {
		Rules: [] Rule { {
			Selector: ClassSelector { Name: "outer" },
			SubRules: [] Rule {
				{ Selector: ClassSelector { Name: "inner-A" } },
				{ Selector: ClassSelector { Name: "inner-B" },  SubRules: [] Rule {
					{ Selector: ClassSelector { Name: "inner-B-inner" } },
				} },
			},
		} },
	})
}

func TestNestedPseudoClass(t *testing.T) {
	var expected = expectSelectors (
		".outer",
		".outer:A",
		".outer:not(:A)",
		".outer .inner",
		".outer .inner:B",
		".outer:C",
		".outer:C .foo",
		".outer:D",
		".outer:D:D1",
	)
	testCompare(t, expected, StyleSheet {
		Rules: [] Rule { {
			Selector: ClassSelector { Name: "outer" },
			SubRules: [] Rule {
				{ Selector: PseudoClassSelector { Name: "A" } },
				{ Selector: PseudoClassSelector { Negate: true, Name: "A" } },
				{ Selector: ClassSelector { Name: "inner" }, SubRules: [] Rule {
					{ Selector: PseudoClassSelector { Name: "B" } },
				} },
				{ Selector: PseudoClassSelector { Name: "C" }, SubRules: [] Rule {
					{ Selector: ClassSelector { Name: "foo" } },
				}  },
				{ Selector: PseudoClassSelector { Name: "D" }, SubRules: [] Rule {
					{ Selector: PseudoClassSelector { Name: "D1" } },
				}  },
			},
		} },
	})
}

func TestGeometry(t *testing.T) {
	var expected = ".foo {\n\tposition: absolute;\n\tz-index: 1000;\n\ttop: 50px;\n\tright: 100px;\n\tbottom: 150px;\n\tleft: 200px;\n\tmargin: 5px;\n\tpadding: 5px 10px;\n\tborder-width: 5px 10px 15px 20px;\n\twidth: 200px;\n\theight: 300px;\n\tmin-width: 20px;\n\tmin-height: 30px;\n\tmax-width: 2000px;\n\tmax-height: 3000px;\n\toverflow: scroll hidden;\n\tborder-style: solid;\n}\n"
	var px = MeasureOf("px")
	var px4 = QuadMeasureOf("px")
	testCompare(t, expected, StyleSheet {
		Rules: [] Rule { {
			Selector: ClassSelector { Name: "foo" },
			Styles: Styles {
				Geometry:     Geometry {
					Position: Position {
						Positioning: "absolute",
						LayerIndex:  SomeNumber(1000),
						Top:         px(50),
						Right:       px(100),
						Bottom:      px(150),
						Left:        px(200),
					},
					Spacing:  Spacing {
						Margin:      px4(5),
						Padding:     px4(5, 10),
						BorderWidth: px4(5, 10, 15, 20),
					},
					Size: Size {
						Width:     px(200),
						Height:    px(300),
						MinWidth:  px(20),
						MinHeight: px(30),
						MaxWidth:  px(2000),
						MaxHeight: px(3000),
					},
					Overflow: func() (Overflow,bool) {
						return Overflow {
							X: "scroll",
							Y: "hidden",
						}, true
					},
				},
			},
		} },
	})
}

func TestFancyBox(t *testing.T) {
	var expected = ".foo {\n\tborder-width: 1px;\n\toverflow: hidden auto;\n\tborder-style: dotted;\n\tborder-color: hsl(0, 90%, 50%);\n\tbox-shadow: -3px -5px 4px 6px hsl(230, 80%, 60%), inset 0 0 0 0 hsla(0, 0%, 0%, 50%);\n\tbackground: hsla(0, 0%, 0%, 50%);\n}\n.bar {\n\tborder-width: 2px;\n\toverflow: auto hidden;\n\tborder-style: solid;\n\tbackground: linear-gradient(to right, hsl(0, 90%, 50%), hsl(230, 80%, 60%));\n}\n"
	var px = MeasureOf("px")
	var px4 = QuadMeasureOf("px")
	var color1 = ColorOf("color1", 0, 90, 50, nil)
	var color2 = ColorOf("color2", 230, 80, 60, nil)
	var color3 = ColorOf("color3", 0, 0, 0, SomeNumber(50))
	testCompare(t, expected, StyleSheet {
		Palette:  [] ColorDef { color1, color2, color3 },
		Rules:    [] Rule { {
			Selector: ClassSelector { Name: "foo" },
			Styles:   Styles {
				Geometry: Geometry {
					Spacing: Spacing {
						BorderWidth: px4(1),
					},
					// by the way, test for partial overflow setting
					Overflow: func() (Overflow, bool) {
						return Overflow { X: "hidden" }, true
					},
				},
				FancyBox: FancyBox {
					BorderStyle: BorderStyle {
						BorderKind: "dotted",
					},
					BorderColor: color1.Color(),
					BoxShadows:  [] BoxShadow {
						{
							Inset:        false,
							OffsetX:      px(-3),
							OffsetY:      px(-5),
							BlurRadius:   px(4),
							SpreadRadius: px(6),
							Color:        color2.Color(),
						},
						{
							Inset: true,
							Color: color3.Color(),
						},
					},
					Background: func() (Background, bool) {
						return Background { Content:
							BackgroundColor { Color: color3.Color() },
						}, true
					},
				},
			},
		}, {
			Selector: ClassSelector { Name: "bar" },
			Styles: Styles {
				Geometry: Geometry {
					Spacing: Spacing {
						BorderWidth: px4(2),
					},
					// by the way, test for partial overflow setting
					Overflow: func() (Overflow, bool) {
						return Overflow { Y: "hidden" }, true
					},
				},
				FancyBox: FancyBox {
					Background: func() (Background, bool) {
						return Background { Content:
							BackgroundSimpleGradient { Gradient:
								SimpleGradient {
									To:    "right",
									Start: color1.Color(),
									End:   color2.Color(),
								},
							},
						}, true
					},
				},
			},
		} },
	})
}

func TestLayoutFlex(t *testing.T) {
	var expected = ".parent {\n\toverflow: auto;\n\tdisplay: flex;\n\tflex-direction: row-reverse;\n\tflex-wrap: wrap;\n\tjustify-content: space-around;\n\talign-items: baseline;\n\talign-content: center;\n\trow-gap: 1rem;\n\tcolumn-gap: 2rem;\n}\n.parent .child {\n\toverflow: auto;\n\torder: -1;\n\tflex-grow: 2;\n\tflex-shrink: 0;\n\tflex-basis: 50rem;\n\talign-self: stretch;\n}\n"
	var rem = MeasureOf("rem")
	testCompare(t, expected, StyleSheet {
		Rules: [] Rule { {
			Selector: ClassSelector { Name: "parent" },
			Styles: Styles {
				LayoutConfig: LayoutConfig {
					ContainerConfig: func() (LayoutContainerConfig, bool) {
						return FlexContainer {
							FlexDirection:  "row-reverse",
							FlexWrap:       "wrap",
							JustifyContent: "space-around",
							AlignItems:     "baseline",
							AlignContent:   "center",
							RowGap:         rem(1),
							ColumnGap:      rem(2),
						}, true
					},
				},
			},
			SubRules: [] Rule { {
				Selector: ClassSelector { Name: "child" },
				Styles:   Styles {
					LayoutConfig: LayoutConfig {
						ItemConfig: LayoutItemConfig {
							Order:      SomeNumber(-1),
							FlexGrow:   SomeNumber(2),
							FlexShrink: SomeNumber(0),
							FlexBasis:  rem(50),
							AlignSelf:  "stretch",
						},
					},
				},
			} },
		} },
	})
}

func TestLayoutGrid(t *testing.T) {
	var expected = ".parent {\n\toverflow: auto;\n\tdisplay: grid;\n\tgrid-template-rows: 50rem 1fr 1fr 60rem;\n\tgrid-template-columns: 70rem 1fr 80rem 1fr;\n\trow-gap: 1rem;\n\tcolumn-gap: 2rem;\n}\n.parent .child {\n\toverflow: auto;\n\torder: 0;\n\tgrid-area: 1 / 2 / 3 / 4;\n}\n"
	var rem = MeasureOf("rem")
	testCompare(t, expected, StyleSheet {
		Rules: [] Rule { {
			Selector: ClassSelector { Name: "parent" },
			Styles: Styles {
				LayoutConfig: LayoutConfig {
					ContainerConfig: func() (LayoutContainerConfig, bool) {
						return GridContainer {
							TemplateRows: [] Measure {
								rem(50), {}, {}, rem(60),
							},
							TemplateColumns: [] Measure {
								rem(70), {}, rem(80), {},
							},
							RowGap:    rem(1),
							ColumnGap: rem(2),
						}, true
					},
				},
			},
			SubRules: [] Rule { {
				Selector: ClassSelector { Name: "child" },
				Styles:   Styles {
					LayoutConfig: LayoutConfig {
						ItemConfig: LayoutItemConfig {
							Order:    SomeNumber(0),
							GridArea: func() (GridArea, bool) {
								return GridArea {
									StartRow:    1,
									StartColumn: 2,
									EndRow:      3,
									EndColumn:   4,
								}, true
							},
						},
					},
				},
			} },
		} },
	})
}

func TestTextConfig(t *testing.T) {
	var expected = ".box1 {\n\toverflow: auto;\n\tfont-family: \"Font A\", \"Font B\";\n\tfont-size: 16px;\n\tfont-weight: bold;\n\tfont-style: italic;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: hsl(0, 0%, 30%);\n\ttext-decoration: underline;\n}\n.box2 {\n\toverflow: auto;\n\tfont-family: \"serif\";\n\ttext-overflow: ellipsis;\n\twhite-space: pre-wrap;\n\toverflow-wrap: break-word;\n\thyphens: auto;\n}\n"
	var px = MeasureOf("px")
	var color1 = ColorOf("color1", 0, 0, 30, nil)
	testCompare(t, expected, StyleSheet {
		Palette: [] ColorDef { color1 },
		Rules: [] Rule {
			{ Selector: ClassSelector { Name: "box1" }, Styles: Styles {
				TextConfig: TextConfig {
					FontConfig:     TextFontConfig {
						FontFamily: [] string { "Font A", "Font B" },
						FontSize:   px(16),
						FontWeight: "bold",
						FontStyle:  "italic",
					},
					WrapConfig: func() (TextWrapConfig, bool) {
						return SingleLineConfig {}, true
					},
					Color:          color1.Color(),
					TextDecoration: "underline",
				},
			} },
			{ Selector: ClassSelector { Name: "box2" }, Styles: Styles {
				TextConfig: TextConfig {
					FontConfig: TextFontConfig {
						FontFamily: [] string { "serif" },
					},
					WrapConfig: func() (TextWrapConfig, bool) {
						return MultiLineConfig {
							HyphensEnabled: true,
						}, true
					},
				},
			} },
		},
	})
}


